本文同步更新於blog
情境:三隻小豬想要蓋房子
<?php
namespace App\PrototypePattern\Building;
class Wall
{
    /**
     * @var string
     */
    public $material;
    public function __construct(string $material)
    {
        $this->material = $material;
    }
}
<?php
namespace App\PrototypePattern\Building;
use App\PrototypePattern\Building\Wall;
class Building
{
    /**
     * @var string
     */
    public $name;
    /**
     * @var Wall
     */
    public $wall;
    /**
     * @param Wall $wall
     * @param string $name
     */
    public function __construct(Wall $wall, string $name = 'unnamed')
    {
        $this->wall = $wall;
        $this->name = $name;
    }
}
<?php
namespace App\PrototypePattern\Building;
use App\PrototypePattern\Building\Building;
use App\PrototypePattern\Building\Wall;
class Program
{
    /**
     * @return array
     */
    public function run()
    {
        //firstBuilding
        $strawWall = new Wall('straw');
        $firstBuilding = new Building($strawWall, 'oldestPigHouse');
        dump($firstBuilding->name); // oldestPigHouse
        dump($firstBuilding->wall->material); // straw
    }
}
完成後,豬大哥很得意自己對房子的設計,
詢問弟弟們要不要直接拷貝一間。
需求一:拷貝豬大哥的稻草屋
兩個弟弟想了想,拷貝大哥的房子,好像是個省事的方法,
但二弟想用木材 (wood),三弟想用磚塊 (bricks) 來蓋房子。
<?php
namespace App\PrototypePattern\Building;
use App\PrototypePattern\Building\Building;
use App\PrototypePattern\Building\Wall;
class Program
{
    /**
     * @return array
     */
    public function run()
    {
        //firstBuilding
        $strawWall = new Wall('straw');
        $firstBuilding = new Building($strawWall, 'oldestPigHouse');
        //secondBuilding
        $secondBuilding = clone $firstBuilding;
        $secondBuilding->name = 'middlePigHouse';
        $secondBuilding->wall->material = 'wood';
        //thirdBuilding
        $thirdBuilding = clone $firstBuilding;
        $thirdBuilding->name = 'youngestPigHouse';
        $thirdBuilding->wall->material = 'bricks';
        dump($firstBuilding->name); // oldestPigHouse
        dump($firstBuilding->wall->material); // bricks
        dump($secondBuilding->name); // middlePigHouse
        dump($secondBuilding->wall->material); // bricks
        dump($thirdBuilding->name); // youngestPigHouse
        dump($thirdBuilding->wall->material); // bricks
        return [
            'firstBuilding' => $firstBuilding,
            'secondBuilding' => $secondBuilding,
            'thirdBuilding' => $thirdBuilding
        ];
    }
}
這時發生了件很尷尬的事情,
當豬二哥選用木材當建材時,大哥的稻草屋就變成木頭屋了...
而豬小弟選用磚塊當建材時,兩個哥哥們的房子就變成磚頭屋了...
導演表示,這樣故事沒辦法進行下去,請我們修改一下。
需求二:讓弟弟們對建材的修改,不會影響到哥哥
<?php
namespace App\PrototypePattern\Building;
use App\PrototypePattern\Building\Wall;
class Building
{
    /**
     * @var string
     */
    public $name;
    /**
     * @var Wall
     */
    public $wall;
    /**
     * @param Wall $wall
     * @param string $name
     */
    public function __construct(Wall $wall, string $name = 'unnamed')
    {
        $this->wall = $wall;
        $this->name = $name;
    }
    public function __clone()
    {
        $this->wall = clone $this->wall;
    }
}
<?php
namespace App\PrototypePattern\Building;
use App\PrototypePattern\Building\Building;
use App\PrototypePattern\Building\Wall;
class Program
{
    /**
     * @return array
     */
    public function run()
    {
        //firstBuilding
        $strawWall = new Wall('straw');
        $firstBuilding = new Building($strawWall, 'oldestPigHouse');
        //secondBuilding
        $secondBuilding = clone $firstBuilding;
        $secondBuilding->name = 'middlePigHouse';
        $secondBuilding->wall->material = 'wood';
        //thirdBuilding
        $thirdBuilding = clone $firstBuilding;
        $thirdBuilding->name = 'youngestPigHouse';
        $thirdBuilding->wall->material = 'bricks';
        dump($firstBuilding->name); // oldestPigHouse
        dump($firstBuilding->wall->material); // straw
        dump($secondBuilding->name); // middlePigHouse
        dump($secondBuilding->wall->material); // wood
        dump($thirdBuilding->name); // youngestPigHouse
        dump($thirdBuilding->wall->material); // bricks
        return [
            'firstBuilding' => $firstBuilding,
            'secondBuilding' => $secondBuilding,
            'thirdBuilding' => $thirdBuilding
        ];
    }
}
後來弟弟們改用其他建材時,就不會影響到原本哥哥的房子了。
這就是淺複製 (Shallow Copy) 與深複製 (Deep Copy) 的不同。
後來三隻小豬跑去開建設公司,又是另一個故事了。
最後附上類別圖:
(註:若不熟悉 UML 類別圖,可參考UML類別圖說明。)
ʕ •ᴥ•ʔ:一開始clone()方法能直接使用,是因為PHP的魔術方法__clone()唷!